﻿namespace IronLua.Runtime

open System
open System.Text
open System.Collections.Generic
open System.Linq.Expressions
open System.Reflection
open System.Reflection.Emit
open IronLua
open IronLua.Runtime
open IronLua.Parsing
open IronLua.Parsing.Ast

type internal Compiler() =
    (*

        fn = compile(thread, name, src)

        call from lua: lua semantics
        call from clr: clr semantics

        how?
        clr semantics
            interop wrappers
                output clr shims must turn everything into clr-compatible objects at any level of nesting

                retvals >> convert-to-clr

                fun?    clr-shim fun
                table?  interop-dictionary table 
                            with    get key = (convert-to-clr this.table[key]) 
                            and     set(key, val) = (this.[convert-from-clr key] <- convert-from-clr value)
                ?       pass

        lua semantics
            call sites (closure.invoke(thread))

    *)

    (*
        lua closure
            capture upvalues 
                upvalues are either locals of caller 
                         or upvalues of caller 
                         or _ENV from thread 
                         or param of Compile       
    *)

    (*
        generic compiled func structure:

        void fn(Thread t, Closure cl) {
            t.CallStack.EnterFrame(cl);

            try {
             
                /* compiled code here */
             
                t.CallStack.ExitFrame(cl);   
            } catch(LuaException ex) {
                var errHandler = t.ErrorHandler;
                t.PushParameter(LuaValue.FromString(ex.Message));
                try {    
                    errHandler.LuaInvoke();
                } catch(LuaException innerEx) {
                    var defaultErrHandler = t.DefaultErrorHandler;
                    t.PushParameter(LuaValue.FromString(ex.Message));
                    defaultErrHandler.LuaInvoke();
                }
                t.CleanUpOnFuncException();
            }            
        }
    *)

    // used types
    static let callInfoCtor = typeof<CallInfo>.GetConstructor([| typeof<IClosure> |])
    static let threadType = typeof<Thread>
    static let closureInterfaceType = typeof<IClosure>
    static let luaValueType = typeof<LuaValue>
    static let luaExceptionType = typeof<LuaException>

    // properties
    static let defaultErrHandlerProperty = threadType.GetProperty("DefaultErrorHandler", BindingFlags.Instance ||| BindingFlags.NonPublic)
    static let errHandlerProperty = threadType.GetProperty("ErrorHandler", BindingFlags.Instance ||| BindingFlags.NonPublic)    
    static let exMsgProperty = luaExceptionType.GetProperty("Message")

    // methods
    static let threadEnterFrameMethod = threadType.GetMethod("EnterFrame", BindingFlags.Instance ||| BindingFlags.NonPublic)
    static let threadExitFrameMethod = threadType.GetMethod("ExitFrame", BindingFlags.Instance ||| BindingFlags.NonPublic)
    static let threadPushParamMethod = threadType.GetMethod("PushParameter", BindingFlags.Instance ||| BindingFlags.NonPublic)
    static let threadCleanUpMethod = threadType.GetMethod("CleanUpOnFuncException", BindingFlags.Instance ||| BindingFlags.NonPublic)
    static let defaultErrHandlerPropertyGet = defaultErrHandlerProperty.GetMethod
    static let errHandlerPropertyGet = errHandlerProperty.GetMethod
    static let exMsgPropertyGet = exMsgProperty.GetMethod
    static let luaValueFromString = luaValueType.GetMethod("FromString", BindingFlags.Static ||| BindingFlags.NonPublic)
    static let closureLuaInvoke = closureInterfaceType.GetMethod("LuaInvoke")

    static let rec cmpLocalDecl fundecl debugmode stmts lhs rhs (il: ILGenerator) =
        // load phase: constants or eval expressions
        // important: a function call can return all its values if it's the last expression
        // (might as well be the only rhs expression)
        // so it's not a 1:1 expr - localdecl relationships, but 1:N

        // store phase: put loaded values into proper containers

        failwith "cmpLocalDecl"

    and compilestmt fundecl debugmode stmts (il: ILGenerator) stmt = 
        match stmt with 
        | Ast.LocalDecl(p, lhs, rhs) -> il |> cmpLocalDecl fundecl debugmode stmts lhs rhs
        | _ ->  failwith "compilestmt"

    static let compilestmts fundecl id name parms upn locn vars debugmode stmts il = 
        stmts |> List.iter (compilestmt fundecl debugmode stmts il) 

    static let compilefn fundecl id name parms upn locn vars stmts debugmode =
        (* thread -> closure -> unit *)
        let dynm = new DynamicMethod(name, null (* = void *), [| typeof<Thread>; typeof<IStorage> |] )
        let il = dynm.GetILGenerator()

        (* tell lua thread we're entering this function *)
        // load thread
        (OpCodes.Ldarg_0) |> il.Emit
        // create callinfo value
        (OpCodes.Ldarg_1) |> il.Emit
        (OpCodes.Newobj, callInfoCtor) |> il.Emit
        // invoke enterframe method
        (OpCodes.Call, threadEnterFrameMethod, null) |> il.EmitCall

        // begin try-catch block
        let beginTryLabel = il.BeginExceptionBlock()

        (* emit the function's body *)
        // il |> compilestmts fundecl id name parms upn locn vars debugmode stmts 

        // load thread
        (OpCodes.Ldarg_0) |> il.Emit        
        // invoke exitframe method
        (OpCodes.Call, threadExitFrameMethod, null) |> il.EmitCall
        // pop callinfo
        (OpCodes.Pop) |> il.Emit

        // success
        (OpCodes.Leave, beginTryLabel) |> il.Emit

        // begin catch block
        let beginCatchLabel = il.BeginCatchBlock(luaExceptionType)
        // get exception
        let luaExLocal = il.DeclareLocal(luaExceptionType)
        (OpCodes.Stloc, luaExLocal) |> il.Emit
        // get current error handler
        let errHandlerLocal = il.DeclareLocal(closureInterfaceType)
        (OpCodes.Ldarg_0) |> il.Emit    
        (OpCodes.Call, errHandlerPropertyGet) |> il.Emit
        (OpCodes.Stloc, errHandlerLocal) |> il.Emit
        // store exception message into a luavalue
        (OpCodes.Ldloc, luaExLocal) |> il.Emit
        (OpCodes.Call, exMsgPropertyGet) |> il.Emit
        (OpCodes.Call, luaValueFromString) |> il.Emit
        // push exception msg as parameter in thread stack
        (OpCodes.Ldarg_0) |> il.Emit    
        (OpCodes.Call, threadPushParamMethod) |> il.Emit

        // begin inner try-catch block
        let beginInnerTryLabel = il.BeginExceptionBlock()
        // invoke error handler
        (OpCodes.Ldloc, errHandlerLocal) |> il.Emit
        (OpCodes.Call, closureLuaInvoke) |> il.Emit
        // success
        (OpCodes.Leave, beginInnerTryLabel) |> il.Emit

        // begin catch block
        let beginInnerCatchLabel = il.BeginCatchBlock(luaExceptionType)

        // get exception
        let luaInnerExLocal = il.DeclareLocal(luaExceptionType)
        (OpCodes.Stloc, luaInnerExLocal) |> il.Emit
        // get current error handler
        let defaultErrHandlerLocal = il.DeclareLocal(closureInterfaceType)
        (OpCodes.Ldarg_0) |> il.Emit    
        (OpCodes.Call, defaultErrHandlerPropertyGet) |> il.Emit
        (OpCodes.Stloc, defaultErrHandlerLocal) |> il.Emit
        // store exception message into a luavalue
        (OpCodes.Ldloc, luaInnerExLocal) |> il.Emit
        (OpCodes.Call, exMsgPropertyGet) |> il.Emit
        (OpCodes.Call, luaValueFromString) |> il.Emit
        // push exception msg as parameter in thread stack
        (OpCodes.Ldarg_0) |> il.Emit    
        (OpCodes.Call, threadPushParamMethod) |> il.Emit
        // invoke error handler
        (OpCodes.Ldloc, defaultErrHandlerLocal) |> il.Emit
        (OpCodes.Call, closureLuaInvoke) |> il.Emit

        // call cleanup func
        (OpCodes.Ldarg_0) |> il.Emit 
        (OpCodes.Call, threadCleanUpMethod) |> il.Emit

        il.EndExceptionBlock()

        // call cleanup func
        (OpCodes.Ldarg_0) |> il.Emit 
        (OpCodes.Call, threadCleanUpMethod) |> il.Emit

        il.EndExceptionBlock()

        // return from method
        (OpCodes.Ret) |> il.Emit

        dynm.CreateDelegate(typeof<Func>) :?> Func

    static member Compile(thread : Thread, _env : LuaValue, src : string) =
        let name = "[string \"" + (if src.Length >= 46 then src.Substring(0, 43) + "..." else src) + "\"]"
        Compiler.Compile(thread, _env, name, src)

    static member Compile(thread : Thread, _env : LuaValue, name : string, src : string) : IClosure =          
        let ast = src |> Parser.parseString (RegexLexer.Tokenize) name
        match ast with 
        | Ast.FunDecl(p, id, callee, name, parms, block, upn, locn, vars) as fundecl -> 
            match callee with
            | Ast.Empty(p) ->   ignore()
            | _ ->              LuaRuntimeException("top level function expected") |> raise
            let stmts =         match block with
                                | Ast.Block(p, stmts) ->    stmts
                                | _ ->                      LuaRuntimeException("code block expected") |> raise
            let fn = compilefn fundecl id name parms upn locn vars stmts (*debugmode?*) true
            let debugfn = compilefn fundecl id name parms upn locn vars stmts (*debugmode?*) false
            failwith "compile"
        | _ -> IronLua.LuaRuntimeException("function expected") |> raise
        failwith "compile"
        